#include "g_local.h"
#include "bg_lua.h"

// Auxiliary data count
int g_luaGDataCount	= 0;

enum
{
	LSYSCALL_SV_NEWREPLICATEDOBJECT			= 1000,	// ()
	LSYSCALL_SV_SETREPTABLESIZE				= 1001,	// (objectid, rtsize)
};


enum
{
	LFIELDTYPE_ORIGIN = LFIELDTYPE_SPECIAL,
	LFIELDTYPE_EFLAGS,
};


gentity_t *G_LuaValidateWorldObjectOID(lua_Number oid)
{
	int intOID;
	gentity_t *result;

	lua_number2int(intOID, oid);
	if(intOID < 0 || intOID >= MAX_GENTITIES)
		Com_Error(ERR_DROP, "Bad object ID referenced");
	result = g_entities + intOID;
	if(!result->inuse)
		Com_Error(ERR_DROP, "Freed object was referenced");

	return result;
}


int G_LuaWorldObjectPropertyValue(lua_State *L, int oid, const bg_luaSpecialProperty_t *sp)
{
	gentity_t *ent = G_LuaValidateWorldObjectOID(oid);

	switch(sp->readHandlingType)
	{
	case LFIELDTYPE_ORIGIN:
		if(ent->client)
			BG_PushVector3v(L, ent->client->ps.origin);
		else
			BG_PushVector3v(L, ent->r.currentOrigin);
		return 3;
	default:
		if(ent->client && sp->pFieldOffset != -1)
			return BG_LuaReadSpecialProperty(L, ent->client, sp, sp->pFieldOffset);
		else
			return BG_LuaReadSpecialProperty(L, ent, sp, sp->eFieldOffset);
	};
}

int G_LuaSetWorldObjectProperty(lua_State *L, int oid, const bg_luaSpecialProperty_t *sp, int valueIndex)
{
	gentity_t *ent = G_LuaValidateWorldObjectOID(oid);
	vec3_t v3;

	switch(sp->writeHandlingType)
	{
	case LFIELDTYPE_ORIGIN:
		BG_GetVector3v(L, valueIndex, v3);
		trap.UnlinkEntity(ent);

		if(ent->client)
		{
			VectorCopy(v3, ent->client->ps.origin);
			BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qtrue);
		}
		else
		{
			VectorCopy(v3, ent->s.pos.trBase);
			ent->s.pos.trTime = level.time;
		}

		VectorCopy(v3, ent->r.currentOrigin);
		trap.LinkEntity(ent);
		return 0;

	default:
		if(ent->client && sp->pFieldOffset != -1)
			return BG_LuaWriteSpecialProperty(L, valueIndex, ent->client, sp, sp->pFieldOffset);
		else
			return BG_LuaWriteSpecialProperty(L, valueIndex, ent, sp, sp->eFieldOffset);
	};
}


// Class definitions
static const bg_luaSpecialProperty_t g_luaWorldObjectProperties[] =
{
	{ "_netClassID", TFOFS(gentity_t, s.scriptClassID), -1, LFIELDTYPE_INTEGER, LFIELDTYPE_INTEGER, NULL },
	{ "origin", TFOFS(gentity_t, r.currentOrigin), TFOFS(gclient_t, ps.origin), LFIELDTYPE_VECTOR3, LFIELDTYPE_ORIGIN, "vector3" },
	{ "efTeleport", TFOFS(gentity_t, s.eFlags), TFOFS(gclient_t, ps.eFlags), LFIELDTYPE_BITFIELD, LFIELDTYPE_BITFIELD, NULL, EF_TELEPORT_BIT },
	{ "moveType", TFOFS(gentity_t, moveType), TFOFS(gclient_t, ps.pm_type), LFIELDTYPE_INTEGER, LFIELDTYPE_INTEGER, NULL },
};


static const bg_luaSystemClass_t g_luaSystemClasses[] =
{
	{
		"WorldObject",
		g_luaWorldObjectProperties,
		(sizeof(g_luaWorldObjectProperties) / sizeof(g_luaWorldObjectProperties[0])),
		G_LuaWorldObjectPropertyValue,
		G_LuaSetWorldObjectProperty,
	},
};

#define NUM_SYSTEM_CLASSES (sizeof(g_luaSystemClasses) / sizeof(g_luaSystemClasses[0]))

int G_LuaSysCall(lua_State *L)
{
	int callID;
	int oid, i;
	gentity_t *ent;
	int *stats;
	lua_Number n;
	char ccmd[MAX_STRING_CHARS];
	int broadcastTarget;
	qboolean multicast;

	if(lua_type(L, 1) != LUA_TNUMBER)
	{
		lua_pushstring(L, "SysCall with non-numeric call ID");
		lua_error(L);
	}

	n = lua_tonumber(L, 1);
	lua_number2int(callID, n);

	switch(callID)
	{
	case LSYSCALL_LOCALE:
		lua_pushstring(L, "server");
		return 1;
	case LSYSCALL_SPECIALPROPERTYVALUE:
		return BG_LuaSpecialPropertyValue(L, g_luaSystemClasses, NUM_SYSTEM_CLASSES);
	case LSYSCALL_SETSPECIALPROPERTY:
		return BG_LuaSetSpecialProperty(L, g_luaSystemClasses, NUM_SYSTEM_CLASSES);
	case LSYSCALL_OBJECTCLIENTID:
		n = lua_tonumber(L, 2);
		lua_number2int(oid, n);
		if(oid < 0 || oid >= level.maxclients)
			lua_pushnil(L);
		else
			lua_pushnumber(L, oid);
		return 1;
	case LSYSCALL_REPLICATEEVENT:
		BG_LuaBuildEventCommand(L, ccmd, sizeof(ccmd));

		// Check parameters

		multicast = qfalse;
		if(lua_istable(L, 4))
		{
			// Check multicast
			lua_getfield(L, 4, "multicast");
			if(lua_toboolean(L, -1))
				multicast = qtrue;
			lua_pop(L, 1);	// Pop multicast flag
		}

		if(multicast)
		{
			if(!lua_istable(L, 5))
			{
				lua_pushstring(L, "Multicast event doesn't have a valid client target");
				lua_error(L);
			}

			// Get the first parameter's object ID
			lua_getfield(L, 5, "_META");
			// _META
			lua_getfield(L, -1, "nativeObjectID");
			// _META / _META.nativeObjectID
			broadcastTarget = lua_tointeger(L, -1);
			lua_pop(L, 2);

			if(broadcastTarget < 0 || broadcastTarget >= MAX_CLIENTS)
			{
				lua_pushstring(L, "Non-multicast event sent to invalid client target");
				lua_error(L);
			}
		}
		else
			broadcastTarget = -1;

		trap.SendServerCommand(broadcastTarget, ccmd);
		return 0;
	case LSYSCALL_SV_SETREPTABLESIZE:
		ent = G_LuaValidateWorldObjectOID(lua_tonumber(L, 2));
		i = lua_tonumber(L, 3);

		if(i < 0 || i > MAX_GDATA)
		{
			lua_pushstring(L, "GDATA size for SetRepTableSize is out of range");
			lua_error(L);
		}

		ent->s.numGData = i;
		return 0;
	case LSYSCALL_SETGDATAPROPERTY:
		ent = G_LuaValidateWorldObjectOID(lua_tonumber(L, 2));
		stats = NULL;
		if(ent->client)
			stats = ent->client->ps.stats;
		return BG_LuaSetGDataProperty(L, ent->s.gdata, stats);
	case LSYSCALL_GDATAPROPERTYVALUE:
		ent = G_LuaValidateWorldObjectOID(lua_tonumber(L, 2));
		stats = NULL;
		if(ent->client)
			stats = ent->client->ps.stats;
		return BG_LuaGDataPropertyValue(L, ent->s.gdata, stats);
	default:
		return BG_LuaSysCall(L, callID);
	};
}


lua_State *g_luaState = NULL;

// LuaAlloc, copped from l_alloc in the reference manual
// (c) 2006-2007 Lua.org, PUC-Rio
void *G_LuaAlloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
	if (nsize == 0)
	{
		free(ptr);
		return NULL;
	}
	else
		return realloc(ptr, nsize);
}

int G_LuaAtPanic(lua_State *L)
{
	const char *err = luaL_checkstring(L, 1);

	Com_Error( ERR_DROP, "Server Lua error: %s", err );

	return 1;
}

const char *G_LuaReadFromStream(lua_State *L, void *data, size_t *size)
{
	fileHandle_t *fh;
	static char chunk[1000];
	int bytesRead;

	fh = (fileHandle_t *)data;

	bytesRead = trap.FS_Read(chunk, 1000, *fh);

	if(!bytesRead)
		return NULL;

	*size = bytesRead;

	return chunk;
}

void G_LuaLoadScript( const char *filename )
{
	qboolean useInvoke;
	fileHandle_t fh;
	int errors;

	useInvoke = qtrue;
	if(filename[0] == '*')
	{
		useInvoke = qfalse;
		filename++;
	}

	if(trap.FS_FOpenFile(va("lua/%s", filename), &fh, FS_READ) == -1)
		Com_Error(ERR_DROP, "Could not open Lua script %s", filename);

	if(useInvoke)
		lua_getglobal(g_luaState, "traceInvoke");

	errors = lua_load(g_luaState, G_LuaReadFromStream, &fh, filename);
	trap.FS_FCloseFile(fh);

	if(errors)
		Com_Error(ERR_DROP, "Script %s failed to load: %s", filename, luaL_checkstring(g_luaState, -1));

	if(useInvoke)
		lua_call(g_luaState, 1, 0);
	else
		lua_call(g_luaState, 0, 0);
}


void G_LuaLoadManifest( const char *manifestName )
{
	int numBytes;
	fileHandle_t fh;
	char manifestFile[10000];
	char *token, *token_p;

	Com_Printf("Loading manifest lua/%s.manifest...\n", manifestName);

	numBytes = trap.FS_FOpenFile( va("lua/%s.manifest", manifestName), &fh, FS_READ );
	if(numBytes == -1)
		Com_Error(ERR_DROP, "Could not open manifest %s", manifestName);
	if(!numBytes)
	{
		trap.FS_FCloseFile(fh);
		return;
	}

	if(numBytes >= sizeof(manifestFile) - 1)
		Com_Error(ERR_DROP, "Lua manifest %s is too long", manifestFile);

	trap.FS_Read(manifestFile, numBytes, fh);
	manifestFile[numBytes] = '\0';
	trap.FS_FCloseFile(fh);

	token_p = manifestFile;

	for(;;)
	{
		token = COM_Parse(&token_p);
		if(!token_p)
			break;
		Com_Printf("...%s\n", token);
		G_LuaLoadScript(token);
	}
}


void G_LuaInit( void )
{
	int i;

	g_luaGDataCount = 0;

	if(g_luaState)
		lua_close(g_luaState);

	g_luaState = lua_newstate(G_LuaAlloc, NULL);
	if(!g_luaState)
		Com_Error( ERR_DROP, "Lua state could not be initialized" );

	lua_atpanic(g_luaState, G_LuaAtPanic);

	// Load standard library
	BG_LuaLoadStandardLibraries(g_luaState);

	// Set syscall function
	lua_pushcfunction(g_luaState, G_LuaSysCall);
	lua_setglobal(g_luaState, "syscall");

	// Set special properties
	BG_LuaRegisterClasses(g_luaState, g_luaSystemClasses, NUM_SYSTEM_CLASSES);

	// Load all manifests
	G_LuaLoadManifest("common");
	G_LuaLoadManifest("tertiumbellum");
}

qboolean G_LuaSpawnObject( gentity_t *gent )
{
	qboolean result;

	lua_getglobal(g_luaState, "traceInvokeV");
	// TIV
	lua_getglobal(g_luaState, "serverSpawnObject");
	// TIV / SSO
	lua_pushstring(g_luaState, gent->classname);
	// TIV / SSO / Class
	lua_pushinteger(g_luaState, gent - g_entities);
	// TIV / SSO / Class / OID
	lua_call(g_luaState, 3, 1);
	// RV

	result = lua_toboolean(g_luaState, -1);
	lua_pop(g_luaState, 1);

	return result;
}

void G_LuaReceiveEvent(int clientNum, const char *evt)
{
	lua_getglobal(g_luaState, "traceInvokeV");
	lua_getglobal(g_luaState, "executeServerEvent");
	lua_pushinteger(g_luaState, clientNum);
	lua_pushstring(g_luaState, evt);
	lua_call(g_luaState, 3, 0);
}

void G_LuaShutdown( void )
{
	if(g_luaState)
		lua_close(g_luaState);
	g_luaState = NULL;
}
